
/**
 *
 * @author group_0217
 */
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;

public final class JShell {
    // This class is actually a ile system.

    // The current working path.
    private String curLocation;
    // The root Directory of the file system.
    private Directory js;
    // The HashMap contains commands..
    private HashMap<String, String> cr = new HashMap<String, String>();

    /**
     * Initialize the JShell, create the root Directory and the command hashmap.
     */
    public JShell() {
        // Generate a JShell object
        this.js = new Directory("JShell");
        setCurLocation("JShell");
        js.getDir().put("JShell", new Directory("JShell"));
        // Initialize all the commands.
        this.cr.put("mkdir", "makeDir");
        this.cr.put("cd", "cd");
        this.cr.put("ls", "ls");
        this.cr.put("cp", "cp");
        this.cr.put("mv", "mv");
        this.cr.put("pwd", "pwd");
        this.cr.put("cat", "catFile");
        this.cr.put("echo", "echo");
        this.cr.put("ln", "ln");
        this.cr.put("find", "find");
        this.cr.put("rm", "rmMain");
        this.cr.put("grep", "grep");
        this.cr.put("get", "getURL");
        this.cr.put("man", "man");
        this.cr.put("exit", "exit");
    }

    /**
     * Change the current working location, should be able to
     * handle ".." and ".".
     * @param curLocation the current working path to be set.
     */
    public void setCurLocation(String curLocation) {
        // Split the current working location, which may contains ".." and ".".
        String[] path = curLocation.split("/");
        String[] newPath = new String[path.length];
        // The index of the path contains ".." and ".".
        int i = 0;
        // The index of the new path.
        int j = 0;
        while (i <= path.length - 1 && path[i] != null) {
            if (path[i].equals(".")) {
                // If path[i] is ".", we do not need to add anything to the
                // new path, because "." refers the Directory itself.
                i++;
            } else if (path[i].equals("..")) {
                // If path[i] is "..", we have to delete the last item in
                // the new path, because ".." refers to the parent Directory.
                j--;
                newPath[j] = null;
                i++;
            } else {
                // If path[i] not ".." or ".", simply add the item to the 
                // new path.
                newPath[j] = path[i];
                j++;
                i++;
            }
        }
        int k = 0;
        String fpath = "";
        while (k <= newPath.length - 1 && newPath[k] != null) {
            // Create the new full path.
            if (k != newPath.length - 1 && newPath[k + 1] != null) {
                fpath = fpath + newPath[k] + "/";
            } else {
                fpath = fpath + newPath[k];
            }
            k++;
        }
        // Set the current working location after dealing with ".." and ".".
        this.curLocation = fpath;
    }

    /**
     * Return curLocation
     * @return this.curLocation
     */
    public String getCurLocation() {
        //return this.curLocation
        return this.curLocation;
    }

    /**
     * Return the Directory object js in JShell object
     * @return this.js
     */
    public Directory getJs() {
        // return this.js
        return this.js;
    }

    /**
     * Create a commands object comOb and analysis the String com by calling comOb.resCo(js)
     * @param com the command String to be read.
     * @param js the root Directory.
     * @return "good" if the command works fine, otherwire return the error message.
     */
    protected String readCom(String com, JShell js) {
        //response to the input command
        //Commands comOb = new Commands(com);
        try {
            // Deal with all the whitespace and tabs.
            String[] Comd = handleWhiteSpace(com);
            return readCo(Comd, js);
        } catch (Exception e) {
            return e.getMessage();
        }
    }

    /**
     * Run JShell.
     */
    public static void JShellRun() {
        //Make JShell run like a system, recursivly accepting commands
        JShell JDisk = new JShell();
        String com = "JShell";
        while (!com.equals("exit")) {
            System.out.print(JDisk.getCurLocation() + "# ");
            BufferedReader comLine = new BufferedReader(new InputStreamReader(System.in));
            try {
                com = comLine.readLine();
                System.out.println(com + ":");
                String Judgement = JDisk.readCom(com, JDisk);
                if (Judgement.equals("good")) {
                    System.out.println("");
                } else {
                    // If the returning String is not good, sth wrong happend.
                    System.out.println(Judgement + '\n');
                }
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }

    /**
     * 
     * @param com the command Array to be read.(The command String after delete the whitespace and tabs)
     * @param js the root Directory.
     * @return the string which is either good or the error message.
     * @throws ClassNotFoundException if Command class not found.
     * @throws NoSuchMethodException if the calling method not in Command class.
     * @throws IllegalAccessException the calling method in Command class is not public.
     * @throws InvocationTargetException if the method not called properly.
     * @throws NoSuchCommandError if the command doesn't exist.
     */
    protected String readCo(String[] com, JShell js) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, NoSuchCommandError {
        Class cl = Class.forName("Commands");
        Class[] types = new Class[2];
        types[0] = Class.forName("JShell");
        types[1] = (new String[10]).getClass();
        if (!cr.containsKey(com[0])) {
            // If command not found.
            throw new NoSuchCommandError("command not found");
        }
        String methodName = getCom(com[0]);
        String[] NewCom = new String[com.length - 1];
        for (int i = 0; i <= com.length - 2; i++) {
            NewCom[i] = com[i + 1];
        }
        // Get the method in command class.
        Method me = cl.getMethod(methodName, types);
        Commands commands = new Commands();
        Object[] para = new Object[2];
        para[0] = js;
        para[1] = NewCom;
        // Invoke the method.
        return (String) me.invoke(commands, para);
    }

    /**
     * Since there may be any amount of whitespace and tabs, this
     * function deal with them, delete the whitespaces an tabs
     * and put the command into the String array.
     * @param com the command String to be deal with.
     * @return a String array only contains non-whitespace item of the String.
     */
    public String[] handleWhiteSpace(String com) {
        // Replace all the '\t' with ' '.
        String Com = com.replace('\t', ' ');
        // Split the String.
        String[] sc = Com.split(" ");
        String[] newSc = new String[sc.length];
        int i = 0;
        int j = 0;
        while (i <= sc.length - 1) {
            // If sc[i] is eligible item.
            if (!sc[i].equals("") && sc[i] != null) {
                newSc[j] = sc[i];
                j++;
            }
            i++;
        }
        String[] newCom = new String[j];
        for (int k = 0; k <= j - 1; k++) {
            newCom[k] = newSc[k];
        }
        return newCom;
    }

    /**
     * 
     * @param com
     * @return the command of the input.
     */
    public String getCom(String com) {
        return cr.get(com);
    }

    /**
     * run JShell as a project
     * @param args
     */
    public static void main(String[] args) {
        //main body of project  
        JShellRun();
    }
}
